/*
 * dush.c - EBC Utils - EBC tool
 * Clunix, cezanne@clunix.com, 2005.3
 *
 * Copyright (c) 2001-2008 by Clunix Inc. All Rights Reserved.
 */
#include "config.h"

#include <ebc.h>

static ebc_t	*ec;

static const char	*serverip;
static ecm_uint16	ebc_port = EBC_PORT_DEF;

static ecm_strlist_t	dushd_modlist;
static ecm_strlist_t	excluded_list;
static sr_t	*sr_exec_info;
static ecm_mutex_t	mutex;
static ecm_uint32	dush_timeout = 60000;
static ECMBOOL		quick_output_mode, going_to_shutdown, serial_execution;

static ecm_cond_t	cond_done;
static ecm_hashtable_t	hash_sr_res;

void
dush_usage(void)
{
	fprintf(stderr,
"Usage: dush [ <options> ] <command>\n"
" Options:\n"
"   -s <dush ebc server ip>\n" 
"   -p <ebc port>\n"
"   -m <dushd module name>: can be specified multiple times\n"
"   -x <exclueded modules>: can be specified multiple times\n"
"   -t <timeout in secs>\n"
"   -q : show output asap\n"
"   -1 : serial execution\n"
"\n"
" Environment Variables:\n"
"    EBC_PORT: default ebc port\n"
"    DUSH_EBCD_SERVER: ebcd server ip\n"
"    DUSH_TIMEOUT: execution time limit in seconds\n"
" dushd module list: '.nodelist' in current directory\n"
		);
}

static void
parse_args(int argc, char *argv[])
{
	int	c;
	int	errflag = 0;

	while ((c = ecm_getopt(argc, argv, "s:p:m:x:t:q1h:")) != -1) {
		switch(c) {
		case 's':
			serverip = ecm_strdup(ecm_optarg);
			break;
		case 'p':
			if (ecm_sscanf(ecm_optarg, "%hu", &ebc_port) != 1)
				errflag++;
			break;
		case 'm':
			if (dushd_modlist == NULL) {
				dushd_modlist = ecm_strlist_create(ECM_FALSE);
			}
			ecm_strlist_add(dushd_modlist, ecm_optarg);
			break;
		case 'x':
			if (excluded_list == NULL) {
				excluded_list = ecm_strlist_create(ECM_FALSE);
			}
			ecm_strlist_add(excluded_list, ecm_optarg);
			break;
		case 't':
			if (ecm_sscanf(ecm_optarg, "%u", &dush_timeout) != 1)
				errflag++;
			break;
		case 'q':
			quick_output_mode = ECM_TRUE;
			break;
		case '1':
			serial_execution = ECM_TRUE;
			break;
		case 'h':
			dush_usage();
			exit(0);
			break;
		default: 
			errflag++;
			break;
		}
	}

	if (serial_execution && quick_output_mode) {
		ecm_fprintf(ecm_stderr, "quick output mode is disallowed with serial execution");
		exit(2);
	}

	if (errflag || ecm_optind >= argc) {
		dush_usage();
		exit(1);
	}

	if (ecm_empty_str(serverip))
		serverip = ecm_getenv("DUSH_EBCD_SERVER");
}

static sr_t *
build_args(int argc, char *argv[])
{
	sr_t	*sr_args;
	int	i;

	sr_args = sr_create_dir();
	for (i = ecm_optind; i < argc; i++) {
		sr_append(sr_args, sr_create_str(ST_STR, argv[i]));
	}
	return sr_args;
}

static void
print_output(const char *modname, sr_t *sr_outputs)
{
	sr_t	*sr_output;

	ecm_fprintf(ecm_stdout, "%s: -=-=-=-=-=-=-=-=-=-\n", modname);
	SR_FOREACH_CHILD(sr_outputs, NULL, sr_output) {
		ecm_fwrite(sr_get_oct_data(sr_output), sr_get_oct_size(sr_output), 1, ecm_stdout);
	}
	ecm_fflush(ecm_stdout);
}

static void
exec_func(ecm_ptr_t ctx)
{
	const char	*modname = (const char *)ctx;
	sr_t	*sr_res;
	sr_t	*sr_exitcode;
	char	*addrspec;

	ecm_asprintf(&addrspec, "pa/./%s/execute", modname);
	if (ebc_call_nofree(ec, addrspec, sr_exec_info, &sr_res) != EBC_OK) {
		ecm_free(addrspec);
		printf("can't call\n");
		return;
	}

	ecm_free(addrspec);

	sr_exitcode = sr_getc(sr_res, "exitcode");
	if (sr_exitcode == NULL) {
		printf("can't execute\n");
		sr_free(sr_res);
		return;
	}

	if (quick_output_mode) {
		ecm_mutex_lock(mutex);
		print_output(modname, sr_getc(sr_res, "outputs"));
		ecm_mutex_unlock(mutex);
		sr_free(sr_res);
	}
	else {
		ecm_mutex_lock(mutex);
		ecm_hashtable_str_insert(hash_sr_res, sr_res, modname);
		ecm_cond_signal(cond_done);
		ecm_mutex_unlock(mutex);
	}
}

static void
print_outputs(void)
{
	const char	*modname;
	sr_t		*sr_res;

	for (modname = ecm_strlist_first(dushd_modlist);
	     modname;
	     modname = ecm_strlist_next(dushd_modlist)) {
		if (ecm_strlist_find(excluded_list, modname) >= 0)
			continue;
		ecm_mutex_lock(mutex);
		while (!going_to_shutdown && (sr_res = ecm_hashtable_str_get(hash_sr_res, modname)) == NULL)
			ecm_cond_wait(cond_done, mutex, NULL);
		if (going_to_shutdown) {
			ecm_mutex_unlock(mutex);
			return;
		}
		print_output(modname, sr_getc(sr_res, "outputs"));
		sr_free(sr_res);
		ecm_mutex_unlock(mutex);
	}
}

static void
cmd_execute(int argc, char *argv[])
{
	ecm_workers_t	workers;
	const char	*modname;

	workers = ecm_init_workers(0, NULL, NULL, NULL);
	sr_exec_info = sr_create_dir();
	sr_addc(sr_exec_info, "args", build_args(argc, argv));
	sr_addc(sr_exec_info, "timeout", sr_create_int(ST_INT, dush_timeout));
	for (modname = ecm_strlist_first(dushd_modlist);
	     modname;
	     modname = ecm_strlist_next(dushd_modlist)) {
		if (ecm_strlist_find(excluded_list, modname) >= 0)
			continue;
		if (going_to_shutdown)
			return;
		if (serial_execution)
			exec_func((ecm_ptr_t)modname);
		else
			ecm_start_worker(workers, exec_func, (ecm_ptr_t)modname);
	}

	if (!quick_output_mode) {
		print_outputs();	
	}

	ecm_clear_all_workers(workers);
	sr_free(sr_exec_info);
	sr_exec_info = NULL;
}

static ECMBOOL
setup_dushd_modlist(void)
{
	char	buf[256], *line;
	FILE	*fp;
	
	fp = ecm_fopen(".nodelist", "r");
	if (fp == NULL)
		return ECM_FALSE;
	dushd_modlist = ecm_strlist_create(ECM_FALSE);
	while ((ecm_fgets(buf, sizeof buf, fp))) {
		line = ecm_trim(buf);
		if (*line == '#')
			continue;
		ecm_strlist_add(dushd_modlist, "%s", line);
	}
	return ECM_TRUE;
}

static void
signal_handler(ecm_signo_t signo)
{
	switch (signo) {
	case ECM_SIGINT:
	case ECM_SIGTERM:
		ecm_mutex_lock(mutex);
		going_to_shutdown = ECM_TRUE;
		if (quick_output_mode)
			ecm_cond_signal(cond_done);
		ecm_mutex_unlock(mutex);
		ebc_cancel(ec);
		break;
	default:
		break;
	}
}

int
main(int argc, char *argv[])
{
	ecm_initialize();

	/* set default ebc_port */
	ecm_sscanf(ecm_getenv(ECM_EBC_PORT_ENV), "%hu", &ebc_port);

	parse_args(argc, argv);

	mutex = ecm_mutex_create(ECM_FALSE);

	if (!quick_output_mode) {
		hash_sr_res = ecm_hashtable_create(16);
		cond_done = ecm_cond_create();
	}

	if (dushd_modlist == NULL && !setup_dushd_modlist()) {
		ecm_fprintf(ecm_stderr, ".nodelist missing");
		return 1;
	}

	if (ebc_connect(serverip, ebc_port, NULL, 5000, (ecm_ebc_t *)&ec) != EBC_OK) {
		fprintf(stderr, "ebc_connect to %hu error\n", ebc_port);
		exit(1);
	}

	ecm_signal(ECM_SIGINT, signal_handler);
	ecm_signal(ECM_SIGTERM, signal_handler);

	cmd_execute(argc, argv);

	ebc_close(ec);
	return 0;
}
